<!DOCTYPE html>
<html lang="zh">
<head>
    <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
    <meta content="IE=edge" http-equiv="X-UA-Compatible">
    <meta content="yes" name="apple-mobile-web-app-capable">
    <meta content="yes" name="apple-touch-fullscreen">
    <meta content="default" name="apple-mobile-web-app-status-bar-style">
    <link href="js/layer/css/layui.css" rel="stylesheet">
    <link href="css/bootstrap.min.css" rel="stylesheet" type="text/css">
    <link href="css/style.min.css" rel="stylesheet" type="text/css">
    <style>
        .layui-tab-brief > .layui-tab-title .layui-this{
            font-size: 18px;
            color: #000!important;
            font-weight: bold;
        }
    </style>

</head>

<body>
<div class="layui-container ws-container container-fluid">

    <div class="layui-code-preview" style="background: #fff;padding: 2%;">
        <div class="layui-tab layui-tab-brief" lay-filter="LAY-CODE-DF-1">
            <div class="layui-tab-title">
                <li class="layui-this" >简介概述</li>
            </div>
        </div>
        <div class="layui-code-item layui-code-item-preview layui-border layui-show" style="">
            本接口系统采用RESTFUL应用程序的设计风格开发，返回结构为JSON格式， 所有资源都共享统一的接口，以便在客户端和服务器之间传输状态。使用的是标准的 HTTP 方法，比如 GET、PUT、POST 和 DELETE。
            <br />
            HTTP请求方式

            对于资源的具体操作类型，由HTTP动词表示<br />

            常用的HTTP动词有下面四个如：<br />
            <strong>GET</strong>&nbsp;&nbsp;&nbsp;&nbsp;/product/ID：获取某个指定商品的信息<br />

            <strong>POST</strong>&nbsp;&nbsp;&nbsp;&nbsp;/product：新建一个商品<br />

            <strong>PUT</strong>&nbsp;&nbsp;&nbsp;&nbsp;/product/ID：更新某个指定商品的信息<br />

            <strong>DELETE</strong>&nbsp;&nbsp;&nbsp;&nbsp;/product/ID：删除某个商品<br />
            <br />
            <h6>请求伪装</h6>
            POST请求参数中添加 _method = 方法名 可以伪造请求方法，这里以HTML表单参数举例：
            如:
            <pre class="layui-code code-demo" lay-options="{}">
<!--此条请求使用POST传输方式，调用HTTP的PUT方法-->
<form method="post" action="http://api.zhanshop.cn/v1/user.info/1">
    <input type="hidden" name="_method" value="PUT" >
    <input type="text" name="nick" value="新的昵称" >
    <input type="submit" value="提交">
</form>
            </pre>
        </div>

    </div>

    <div class="layui-code-preview" style="background: #fff;padding: 2%;">
        <div class="layui-tab layui-tab-brief" lay-filter="LAY-CODE-DF-1">
            <div class="layui-tab-title">
                <li class="layui-this" >开发参数</li>
            </div>
        </div>
        <div class="layui-code-item layui-code-item-preview layui-border layui-show" style="">

            在发起接口请求时，开发者需准备必要参数，如签名版本、签名密钥等，获取方式请与后端沟通
            <table class="layui-table">

                <colgroup>
                    <col width="20%">
                    <col width="80%">
                </colgroup>

                <thead>

                <tr>
                    <th>参数名</th>
                    <th>用途</th>
                </tr>
                </thead>
                <tbody>
                <tr>

                    <td>app名称</td>

                    <td>用于前端交互app认证</td>

                </tr>
                <tr>

                    <td>签名版本</td>

                    <td>用于前端交互签名版本认证</td>

                </tr>

                <tr>

                    <td>签名密钥</td>

                    <td>用于前端交互签名版本签名认证</td>

                </tr>

                </tbody>

            </table>
        </div>

    </div>

    <div class="layui-code-preview" style="background: #fff;padding: 2%;">
        <div class="layui-tab layui-tab-brief" lay-filter="LAY-CODE-DF-1">
            <div class="layui-tab-title">
                <li class="layui-this" >签名算法</li>
            </div>
        </div>
        <div class="layui-code-item layui-code-item-preview layui-border layui-show" style="">

            HMAC签名在每个Http接口请求的Header中添加如下内容
            <table class="layui-table">

                <colgroup>
                    <col width="10%">
                    <col width="20%">
                    <col width="50%">
                    <col width="20%">
                </colgroup>

                <thead>

                <tr>
                    <th>Header</th>
                    <th>内容</th>
                    <th>说明</th>
                    <th>示例</th>
                </tr>
                </thead>
                <tbody>
                <tr>

                    <td>app-name</td>
                    <td>APP的名称</td>
                    <td>这里填写一个后端已经注册的APP名</td>
                    <td>zhanshop</td>

                </tr>
                <tr>

                    <td>app-time</td>
                    <td>请求发起的时间戳（秒）</td>
                    <td>时间戳要求在接口请求的2分钟内</td>
                    <td>1515640542</td>

                </tr>

                <tr>

                    <td>app-sign</td>
                    <td>接口参数签名</td>
                    <td>签名算法参考下面的签名算法示例代码</td>
                    <td>v1:G0UrBD3Tg9cyl6me2lNKGikUszQ=</td>

                </tr>

                </tbody>

            </table>
            <br />
            <h6>HMAC签名示例</h6>
            <div class="layui-tab layui-tab-card">

                <ul class="layui-tab-title" style="padding: 0px;">

                    <li class="layui-this">bash</li>

                    <li>javascript</li>

                    <li>php</li>

                    <li>python</li>

                    <li>java</li>

                    <li>go</li>

                </ul>

                <div class="layui-tab-content" style="padding: 0px;">

                    <div class="layui-tab-item layui-show">
                        <pre class="layui-code code-demo" lay-options="{}" style="border: none;">
#!/bin/bash
appName="zhanshop"
appTime=$(date +%s) # 当前时间戳注意尽可能是一个标准时间戳不能相差太大
secretVersion="v1" # 签名所使用的版本号
secretKey="068c01437878c3985abe18b490b1480a" # 签名所使用的密钥值
requestData='{"channel":"xiaomi","type":"phone","data":{"area":"+86","phone":"14000000000","code":"336699"}}'
signStr="${appName}${appTime}/v1/passport.login${requestData}"
appSign=$(echo -n "$signStr" | openssl dgst -hmac "$secretKey" -sha1 -binary | openssl enc -base64)
# 触发POST请求，并在header中携带token
curl -X POST -H "app-name: ${appName}" -H "app-time: ${appTime}" -H "app-sign: ${secretVersion}:${appSign}" -H "Content-Type: application/json" -d ${requestData} "http://127.0.0.1:6201/v1/passport.login?arg=value#anchor"

                        </pre>
                    </div>

                    <div class="layui-tab-item">
                        <pre class="layui-code code-demo" lay-options="{}" style="border: none;">
//先载入https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js
function hash_hmac(key, data, algorithm) {
    var hash = CryptoJS.HmacSHA1(data, key);
    return CryptoJS.enc.Base64.stringify(hash);
}
var appName="zhanshop";
var appTime=Math.floor(Date.now() / 1000); // 当前时间戳注意尽可能是一个标准时间戳不能相差太大
var secretVersion="v1"; // 签名所使用的版本号
var secretKey="068c01437878c3985abe18b490b1480a"; // 签名所使用的密钥值
var requestData='{"channel":"xiaomi","type":"phone","data":{"area":"+86","phone":"14000000000","code":"336699"}}';
var data = appName+appTime+"/v1/passport.login"+requestData;
var hmac = hash_hmac(secretKey, data);


$.ajax({
    url: 'http://127.0.0.1:6201/v1/passport.login?arg=value#anchor', // 目标URL
    type: 'POST', // 请求类型
    contentType: 'application/json', // 发送信息至服务器时内容编码类型
    data: requestData, // 将要发送的数据
    dataType: 'json', // 预期服务器返回的数据类型
    headers: {
        "app-name": appName,
        "app-time": appTime,
        "app-sign": secretVersion+":"+hmac,
    },
    success: function(response) {
        // 请求成功时的回调函数
        console.log(response);
    },
    error: function(xhr, status, error) {
        // 出错时的回调函数
        console.error(error);
    }
});
                        </pre>
                    </div>

                    <div class="layui-tab-item">
                        <pre class="layui-code code-demo" lay-options="{}" style="border: none;">
$url = 'http://127.0.0.1:6201/v1/passport.login?arg=value#anchor';
$requestData = '{"channel":"xiaomi","type":"phone","data":{"area":"+86","phone":"14000000000","code":"336699"}}';
$appName = "zhanshop";
$appTime = time(); # 当前时间戳注意尽可能是一个标准时间戳不能相差太大
$secretVersion = "v1"; # 签名所使用的版本号
$secretKey = "068c01437878c3985abe18b490b1480a"; # 签名所使用的密钥值
$appSign = $secretVersion.':'.base64_encode(hash_hmac("sha1", $appName.$appTime.parse_url($url, PHP_URL_PATH).$requestData, $secretKey, true));
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type:application/json', 'app-name:'.$appName, 'app-time:'.$appTime, 'app-sign:'.$appSign]);
curl_setopt($ch, CURLOPT_POSTFIELDS, $requestData);
$resp = curl_exec($ch);
curl_close($ch);
                        </pre>
                    </div>

                    <div class="layui-tab-item">
                        <pre class="layui-code code-demo" lay-options="{}" style="border: none;">
import hmac
import hashlib
import base64
import time
import requests

def hmac_sha1_base64(key, message):
    hmac_sha1 = hmac.new(key.encode('utf-8'), msg=message.encode('utf-8'), digestmod=hashlib.sha1)
    base64_digest = base64.b64encode(hmac_sha1.digest()).decode('utf-8')
    return base64_digest

# 使用示例
url = "http://127.0.0.1:6201/v1/passport.login?arg=value#anchor";
requestData = '{"channel":"xiaomi","type":"phone","data":{"area":"+86","phone":"14000000000","code":"336699"}}'
appName = "zhanshop"
appTime = str(int(time.time()))  # 当前时间戳注意尽可能是一个标准时间戳不能相差太大
secretVersion = "v1"  # 签名所使用的版本号
secretKey = "068c01437878c3985abe18b490b1480a"  # 签名所使用的密钥值

message = appName+appTime+"/v1/passport.login"+requestData
signature = secretVersion+":"+hmac_sha1_base64(secretKey, message)
headers = {
    "app-name": appName,
    "app-time": appTime,
    "app-sign": signature,
    'Content-Type': 'application/json'
};
resp = requests.post(url, headers=headers, data=requestData)
print(resp.content)
                        </pre>
                    </div>

                    <div class="layui-tab-item">
                        <pre class="layui-code code-demo" lay-options="{}" style="border: none;">
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class Main {
    public static String hmacSha1Base64(String data, String secretKey) {
        try {
            // 创建一个HMAC-SHA1密钥
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1");
            // 获得一个Mac对象
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(secretKeySpec);
            // 执行HMAC-SHA1计算
            byte[] hmacBytes = mac.doFinal(data.getBytes());
            // 将结果编码为Base64
            return Base64.getEncoder().encodeToString(hmacBytes);
        } catch (Exception e) {
            throw new RuntimeException("Failed to calculate HMAC-SHA1", e);
        }
    }

    public static void main(String[] args) {
        //使用示例
        String url = "http://127.0.0.1:6201/v1/passport.login?arg=value#anchor";
        String requestData = "{\"channel\":\"xiaomi\",\"type\":\"phone\",\"data\":{\"area\":\"+86\",\"phone\":\"14000000000\",\"code\":\"336699\"}}";
        String appName = "zhanshop";
        String appTime = String.valueOf(System.currentTimeMillis() / 1000);  // 当前时间戳注意尽可能是一个标准时间戳不能相差太大
        String secretVersion = "v1";  //签名所使用的版本号
        String secretKey = "068c01437878c3985abe18b490b1480a";  //签名所使用的密钥值

        String data = appName+appTime+"/v1/passport.login"+requestData;
        String hmacSha1Hex = hmacSha1Base64(data, secretKey);
        String appSign = secretVersion+":"+hmacSha1Hex;

        try {
            URL httpurl = new URL(url);
            HttpURLConnection conn = (HttpURLConnection) httpurl.openConnection();
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setRequestProperty("app-name", appName);
            conn.setRequestProperty("app-time", appTime);
            conn.setRequestProperty("app-sign", appSign);
            conn.setDoOutput(true);

            byte[] outputInBytes = requestData.getBytes("UTF-8");
            OutputStream os = conn.getOutputStream();
            os.write(outputInBytes);
            os.close();

            int responseCode = conn.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                System.out.println("Request sent successfully");
            } else {
                System.out.println("Request failed with error code: " + responseCode);
            }
        } catch (Exception e) {
            // 异常处理
        }
    }
}
                        </pre>
                    </div>

                    <div class="layui-tab-item">
                        <pre class="layui-code code-demo" lay-options="{}" style="border: none;">
package main

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"time"
)

func main() {
	url := "http://127.0.0.1:6201/v1/passport.login?arg=value#anchor"
	requestData := "{\"channel\":\"xiaomi\",\"type\":\"phone\",\"data\":{\"area\":\"+86\",\"phone\":\"14000000000\",\"code\":\"336699\"}}"
	appName := "zhanshop"
	secretVersion := "v1" //签名所使用的版本号
	now := time.Now()
	appTime := strconv.Itoa(int(now.Unix()))
	data := appName + appTime + "/v1/passport.login" + requestData
	// API密钥
	key := "068c01437878c3985abe18b490b1480a"
	// 使用hmac.New进行HMAC-SHA1加密
	h := hmac.New(sha1.New, []byte(key))
	h.Write([]byte(data))
	// 生成加密后的hash
	hash := h.Sum(nil)
	// 将hash转换为Base64格式
	encoded := base64.StdEncoding.EncodeToString(hash)

	// 将JSON字符串转换为字节切片
	dataBuffer := bytes.NewBuffer([]byte(requestData))

	// 创建HTTP请求
	request, err := http.NewRequest("POST", url, dataBuffer)
	if err != nil {
		fmt.Println(err)
		return
	}

	// 设置请求头信息，指定内容类型和长度
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Content-Length", fmt.Sprintf("%d", dataBuffer.Len()))
	request.Header.Set("app-name", appName)
	request.Header.Set("app-time", appTime)
	request.Header.Set("app-sign", secretVersion+":"+encoded)

	// 执行请求
	client := &http.Client{}
	response, err := client.Do(request)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer response.Body.Close()

	// 读取响应体
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(string(body))

}

                        </pre>
                    </div>

                </div>

            </div>
        </div>

    </div>


    <div class="layui-code-preview" style="background: #fff;padding: 2%;">
        <div class="layui-tab layui-tab-brief" lay-filter="LAY-CODE-DF-2">
            <div class="layui-tab-title">
                <li class="layui-this" >HTTP错误码</li>
            </div>
        </div>
        <div class="layui-code-item layui-code-item-preview layui-border layui-show" style="">
            通用错误，开发者需准备必要参数，如签名版本、签名密钥等，获取方式请与后端沟通
            <table class="layui-table">

                <colgroup>
                    <col width="10%">
                    <col width="25%">
                    <col width="65%">
                </colgroup>

                <thead>

                <tr>
                    <th>错误码</th>
                    <th>说明</th>
                    <th>解决方案</th>
                </tr>
                </thead>
                <tbody>

                <tr>
                    <td>400</td>
                    <td>参数错误</td>
                    <td>请根据接口返回的详细信息检查</td>
                </tr>

                <tr>
                    <td>401</td>
                    <td>签名错误</td>
                    <td>检查签名参数和方法是否都符合签名算法要求，参考：如何生成签名</td>
                </tr>

                <tr>
                    <td>403</td>
                    <td>无权限</td>
                    <td>请根据接口返回的详细信息检查，用户token可能缺失</td>
                </tr>

                <tr>
                    <td>417</td>
                    <td>通用错误</td>
                    <td>请根据接口返回的错误说明弹出错误信息</td>
                </tr>

                <tr>

                    <td>429</td>
                    <td>频率超限</td>
                    <td>请求频率超限，请降低请求接口频率</td>
                </tr>

                <tr>

                    <td>500</td>
                    <td>系统异常，请稍后重试</td>
                    <td>系统异常，可能因参数报错或者后端有BUG请和后端沟通确定</td>
                </tr>

                </tbody>

            </table>
        </div>

    </div>
</div>
<script type="text/javascript" src="js/layer/layui.js"></script>
<script>

    layui.use(function(){
        layui.code({
            elem: '.code-demo'
        });

    })

</script>
</body>
</html>